#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QGLWidget>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>

#include <GL/glut.h>

#include <wrap/gui/trackball.h>
#include <wrap/qt/trackball.h>

#include <WalkaWare/WalkaWare.h>

class GLWidget : public QGLWidget
{
	Q_OBJECT

	public:

		typedef QGLWidget BaseType;
		typedef GLWidget  ThisType;

		GLWidget(QWidget * parent = 0)
			: BaseType     (parent)
			, rgbTex       (0)
			, acceleration (0.0f, 0.0f, 0.0f)
		{
			;
		}

		virtual ~GLWidget(void)
		{
			;
		}

	public slots:

		void setImage(QImage image, quint32 timestamp)
		{
			(void)timestamp;

			this->makeCurrent();

			glBindTexture(GL_TEXTURE_2D, this->rgbTex);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, GLsizei(image.width()), GLsizei(image.height()), 0, GL_RGB, GL_UNSIGNED_BYTE, image.bits());
			glBindTexture(GL_TEXTURE_2D, 0);

			this->updateGL();
		}

		void setPointCloud(WalkaWare::Shared::PointCloud pointCloud, quint32 timestamp)
		{
			(void)timestamp;

			this->pointCloud = pointCloud;
			this->pointCloud.updateBoundingBox();

			this->updateGL();
		}

		void setAcceleration(float x, float y, float z, quint32 timestamp)
		{
			(void)timestamp;

			this->acceleration[0] = x;
			this->acceleration[1] = y;
			this->acceleration[2] = z;
			//qDebug("%.2f %.2f %.2f\n", x, y, z);

			this->updateGL();
		}

		void setPlane(float offset, float normalX, float normalY, float normalZ, quint32 timestamp)
		{
			(void)timestamp;

			this->plane.Set(WalkaWare::Shared::Vec3f(normalX, normalY, normalZ), offset);

			this->updateGL();
		}

	protected:

		vcg::Trackball                tball;
		GLuint                        rgbTex;
		WalkaWare::Shared::PointCloud pointCloud;
		WalkaWare::Shared::Vec3f      acceleration;
		WalkaWare::Shared::Plane3f    plane;

		void drawTriad(void)
		{
			glLineWidth(1.0f);

			glBegin(GL_LINES);
				glColor3f(1.0f, 0.0f, 0.0f);
				glVertex3f(0.0f, 0.0f, 0.0f);
				glVertex3f(1.0f, 0.0f, 0.0f);

				glColor3f(0.0f, 1.0f, 0.0f);
				glVertex3f(0.0f, 0.0f, 0.0f);
				glVertex3f(0.0f, 1.0f, 0.0f);

				glColor3f(0.0f, 0.0f, 1.0f);
				glVertex3f(0.0f, 0.0f, 0.0f);
				glVertex3f(0.0f, 0.0f, 1.0f);
			glEnd();
		}

		void drawAcceleration(void)
		{
			//WalkaWare::Shared::Vec3f acc = this->acceleration.normalized();
			WalkaWare::Shared::Vec3f acc = this->acceleration;

			glLineWidth(2.0f);
			glColor3f(1.0f, 1.0f, 0.0f);
			glBegin(GL_LINES);
				glVertex3f(0.0f, 0.0f, 0.0f);
				glVertex3f(acc[0], -acc[1], acc[2]);
			glEnd();
			glLineWidth(1.0f);
		}

		void drawPointCloud(void)
		{
			if (this->pointCloud.isEmpty()) return;

			const int n = this->pointCloud.size();

			const float zMin = this->pointCloud.boundingBox().min[2];
			const float zDim = this->pointCloud.boundingBox().DimZ();

			glPointSize(2.0f);
			glColor3f(1.0f, 1.0f, 1.0f);
			glBegin(GL_POINTS);
			for (int i=0; i<n; ++i)
			{
				const float s = ((this->pointCloud[i][2] - zMin) / zDim);
				glColor3f(s, s, s);
				glVertex3fv(this->pointCloud[i].V());
			}
			glEnd();
		}

		void drawBox(void)
		{
			const WalkaWare::Shared::Box3f bbox(WalkaWare::Shared::Vec3f(-0.5f, -1.5f, -2.5f), WalkaWare::Shared::Vec3f(0.5f, -0.5f, -1.5f));

			const WalkaWare::Shared::Vec3f bc = bbox.Center();
			const WalkaWare::Shared::Vec3f bs = bbox.Dim();

			glPushMatrix();
				glTranslatef(bc[0], bc[1], bc[2]);
				glScalef(bs[0], bs[1], bs[2]);
				glColor3f(0.0f, 1.0f, 1.0f);
				glutWireCube(1.0f);
			glPopMatrix();
		}

		void drawPlane(void)
		{
			const WalkaWare::Shared::Vec3f up(0.0f, 1.0f, 0.0f);

			const float                    angle = float(acos(up * this->plane.Direction())) * 180.0f / 3.1416f;
			const WalkaWare::Shared::Vec3f axis  = up ^ this->plane.Direction();

			glPushMatrix();
				glRotatef(angle, axis[0], axis[1], axis[2]);
				glTranslatef(0.0f, this->plane.Offset(), 0.0f);
				glScalef(4.0f, 1.0f, 4.0f);
				glTranslatef(0.0f, 0.0f, -0.5f);
				glColor4f(1.0f, 0.5f, 0.0f, 0.5f);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				glEnable(GL_BLEND);
				glBegin(GL_TRIANGLE_STRIP);
					glVertex3f(-0.5f, 0.0f,  0.5f);
					glVertex3f( 0.5f, 0.0f,  0.5f);
					glVertex3f(-0.5f, 0.0f, -0.5f);
					glVertex3f( 0.5f, 0.0f, -0.5f);
				glEnd();
				glDisable(GL_BLEND);
			glPopMatrix();
		}

		virtual void keyPressEvent(QKeyEvent * e)
		{
			const int                   key       = e->key();
			const Qt::KeyboardModifiers modifiers = e->modifiers();

			(void)key;
			(void)modifiers;

			switch (key)
			{
				case Qt::Key_Control:
					this->tball.ButtonDown(QT2VCG (Qt::NoButton, Qt::ControlModifier));
				break;

				case Qt::Key_Shift:
					this->tball.ButtonDown(QT2VCG (Qt::NoButton, Qt::ShiftModifier  ));
				break;

				case Qt::Key_Alt:
					this->tball.ButtonDown(QT2VCG (Qt::NoButton, Qt::AltModifier    ));
				break;

				case Qt::Key_H:
					if (modifiers & Qt::ControlModifier)
					{
						this->tball.Reset();
					}
				break;

				default:
					;
				break;
			}

			this->updateGL();
		}

		virtual void keyReleaseEvent(QKeyEvent * e)
		{
			const int                   key       = e->key();
			const Qt::KeyboardModifiers modifiers = e->modifiers();

			(void)key;
			(void)modifiers;

			switch (key)
			{
				case Qt::Key_Control:
					this->tball.ButtonUp(QT2VCG (Qt::NoButton, Qt::ControlModifier));
				break;

				case Qt::Key_Shift:
					this->tball.ButtonUp(QT2VCG (Qt::NoButton, Qt::ShiftModifier  ));
				break;

				case Qt::Key_Alt:
					this->tball.ButtonUp(QT2VCG (Qt::NoButton, Qt::AltModifier    ));
				break;

				default:
					;
				break;
			}

			this->updateGL();
		}

		virtual void mousePressEvent(QMouseEvent * e)
		{
			const int x = e->x();
			const int y = this->height() - 1 - e->y();

			this->setFocus();
			this->tball.MouseDown(x, y, QT2VCG(e->button(), e->modifiers()));

			this->updateGL();
		}

		virtual void mouseReleaseEvent(QMouseEvent * e)
		{
			const int x = e->x();
			const int y = this->height() - 1 - e->y();

			this->tball.MouseUp(x, y, QT2VCG(e->button(), e->modifiers()));

			this->updateGL();
		}

		virtual void mouseMoveEvent(QMouseEvent * e)
		{
			const int x = e->x();
			const int y = this->height() - 1 - e->y();

			if (e->buttons() != 0)
			{
				this->tball.MouseMove(x, y);
				this->updateGL();
			}
		}

		virtual void wheelEvent(QWheelEvent * e)
		{
			const int wheel_step = 120;
			this->tball.MouseWheel(float(e->delta()) / float(wheel_step), QTWheel2VCG(e->modifiers()));

			this->updateGL();
		}

		virtual void initializeGL(void)
		{
			this->tball.Reset();

			glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

			this->rgbTex = 0;
			glGenTextures(1, &(this->rgbTex));
			glBindTexture(GL_TEXTURE_2D, this->rgbTex);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 640, 480, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
			glBindTexture(GL_TEXTURE_2D, 0);
		}

		virtual void paintGL(void)
		{
			const int w = this->width();
			const int h = this->height();

			glClearColor(0.1f, 0.2f, 0.4f, 0.0f);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

			glViewport(0, 0, w, h);

			/*
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);

			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();

			glEnable(GL_TEXTURE_2D);
			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
			glBindTexture(GL_TEXTURE_2D, this->rgbTex);

			glBegin(GL_TRIANGLE_STRIP);
				glTexCoord2f(0.0f, 0.0f);  glVertex3f(-1.0f, -1.0f, 0.0f);
				glTexCoord2f(1.0f, 0.0f);  glVertex3f( 1.0f, -1.0f, 0.0f);
				glTexCoord2f(0.0f, 1.0f);  glVertex3f(-1.0f,  1.0f, 0.0f);
				glTexCoord2f(1.0f, 1.0f);  glVertex3f( 1.0f,  1.0f, 0.0f);
			glEnd();

			glBindTexture(GL_TEXTURE_2D, 0);
			glDisable(GL_TEXTURE_2D);
			*/

			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			gluPerspective(60.0f, float(w)/float(h), 0.1f, 100.0f);

			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			gluLookAt(0.0f, 0.0f, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

			glEnable(GL_DEPTH_TEST);

			this->tball.GetView();
			this->tball.Apply(true);

			this->drawTriad();
			this->drawAcceleration();
			this->drawPointCloud();
			this->drawBox();
			this->drawPlane();

			glDisable(GL_DEPTH_TEST);
		}
};

#endif // GLWIDGET_H
